/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Zend Technologies
 *******************************************************************************/
package org.eclipse.php.internal.console.ui.editor;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.internal.debug.core.Logger;
import org.eclipse.php.internal.debug.core.PHPDebugCoreMessages;
import org.eclipse.php.internal.debug.core.PHPDebugPlugin;
import org.eclipse.php.internal.debug.core.launching.PHPLaunchUtilities;
import org.eclipse.php.internal.debug.core.preferences.PHPexeItem;
import org.eclipse.php.internal.debug.core.preferences.PHPexes;
import org.eclipse.swt.widgets.Display;

import com.ibm.icu.text.MessageFormat;

/**
 * A local PHP script debugger initializer. This debugger initializer sets the
 * necessary environment and program arguments for the PHP process and then
 * starts the process.
 * 
 * @author Shalom Gibly
 */
public class PHPExecutableDebuggerInitializer {

	protected ILaunch launch;

	/**
	 * Constructs a new debugger initializer.
	 * 
	 * @param launch
	 * @throws IOException
	 */
	public PHPExecutableDebuggerInitializer(ILaunch launch) throws IOException {
		this.launch = launch;
	}

	/**
	 * Initialize the php executable debugger.
	 * 
	 * @param phpExe
	 * @param fileName
	 * @param workingDir
	 * @param query
	 */
	public void initializeDebug(String phpExe, String fileName,
			String workingDir, String query) {
		initializeDebug(phpExe, fileName, workingDir, query, null, null);
	}

	/**
	 * Initialize the php executable debugger.
	 * 
	 * @param phpExe
	 * @param fileName
	 * @param workingDir
	 * @param query
	 * @param envVariables
	 * @param phpIniLocation
	 */
	public void initializeDebug(String phpExe, String fileName,
			String workingDir, String query, Map<String, String> envVariables,
			String phpIniLocation) {
		try {
			File phpExeFile = new File(phpExe);

			// Determine configuration file directory:
			String phpConfigDir = phpExeFile.getParent();
			if (phpIniLocation != null && !phpIniLocation.equals("")) {
				phpConfigDir = new File(phpIniLocation).getParent();
			}

			// Detect PHP SAPI type:
			String sapiType = null;
			PHPexeItem[] items = PHPexes.getInstance().getAllItems();
			for (PHPexeItem item : items) {
				if (item.getExecutable().equals(phpExeFile)) {
					sapiType = item.getSapiType();
					break;
				}
			}

			String[] args = new String[0];

			// Prepare the environment
			Map<String, String> additionalLaunchEnvironment = PHPLaunchUtilities
					.getPHPCGILaunchEnvironment(fileName, query, phpConfigDir,
							phpExeFile.getParent(),
							sapiType == PHPexeItem.SAPI_CGI ? args : null);
			if (envVariables == null) {
				envVariables = additionalLaunchEnvironment;
			} else {
				additionalLaunchEnvironment.putAll(envVariables);
				envVariables = additionalLaunchEnvironment;
			}
			String[] environmetVars = getEnvironment(launch
					.getLaunchConfiguration(), asAttributesArray(envVariables));

			// Prepare the command line.
			String[] phpCmdArray = getCommandLine(launch
					.getLaunchConfiguration(), phpExe, phpConfigDir, fileName,
					sapiType == PHPexeItem.SAPI_CLI ? args : null);
			// String[] phpCmdArray = {
			// "C:/Program Files/Zend/ZendServer/bin/php.exe",
			// "-c",
			// "C:/DOCUME~1/douya/LOCALS~1/Temp/zend_debug/session53613.tmp",
			// "-d", "asp_tags=off", "F:/php/pdt/ws/php/src/classA1.php" };
			// Make sure that we have executable permissions on the file.
			PHPexes.changePermissions(new File(phpCmdArray[0]));

			if (PHPDebugPlugin.DEBUG) {
				System.out
						.println("Executing: " + Arrays.toString(phpCmdArray));
				System.out.println("Process environment: "
						+ Arrays.toString(environmetVars));
			}

			// Execute the command line.
			Process p;
			File workingDirFile = new File(workingDir);
			if (workingDirFile.exists()) {
				if (PHPDebugPlugin.DEBUG) {
					System.out.println("Working directory: " + workingDir);
				}
				p = Runtime.getRuntime().exec(phpCmdArray, environmetVars,
						workingDirFile);
			} else {
				p = Runtime.getRuntime().exec(phpCmdArray, environmetVars);
			}
//			p.waitFor();
			// Attach a crash detector
			new Thread(new ProcessCrashDetector2(launch, p,this)).start();
//			wait();
		} catch (final Exception e) {
			final Display display = Display.getDefault();
			display.asyncExec(new Runnable() {
				public void run() {
					String message = e.getLocalizedMessage();
					if (message != null) {
						message = message.replaceFirst(e.getClass().getName()
								+ ": ", "");
						MessageDialog.openError(display.getActiveShell(),
								"Error", NLS.bind(
										"Error running PHP executable:\n\n{0}",
										message));
					}
				}
			});
			DebugPlugin.log(e);
		} finally {
			// Remove temporary directory
			if (phpIniLocation != null) {
				File phpIniFile = new File(phpIniLocation);
				phpIniFile.deleteOnExit();
				phpIniFile.getParentFile().deleteOnExit();
				
//				File debugFile = new File(fileName);
//				debugFile.deleteOnExit();
			}
		}
	}

	/**
	 * Returns an array of system environment attributes from the given launch
	 * configuration. If empty, then the current native environment attributes
	 * will be returned. From this we append any additional environment
	 * variables we might want to add. Note: Additional environments may
	 * override the native environment attributes, but disregarded when an
	 * equivalent launch configuration attribute is set for the given launch.
	 * 
	 * @param configuration
	 *            the launch configuration
	 * @param additionalEnv
	 *            additional environment strings
	 * @return the complete environment
	 * @throws CoreException
	 *             rethrown exception
	 */
	@SuppressWarnings("unchecked")
	public static String[] getEnvironment(ILaunchConfiguration configuration,
			String[] additionalEnv) throws CoreException {

		if (additionalEnv == null) {
			additionalEnv = new String[0];
		}
		Map<String, String> additionalEnvMap = asAttributesMap(additionalEnv);

		String[] totalEnv = null;

			// We have nothing in the environment tab, so we need to set
			// currentEnv ourselves to the current environment
			Map<String, String> nativeEnvironment = DebugPlugin.getDefault()
					.getLaunchManager().getNativeEnvironmentCasePreserved();
			// Make sure we override any native environment with the additional
			// environment values
			nativeEnvironment.putAll(additionalEnvMap);
			totalEnv = asAttributesArray(nativeEnvironment);
		return totalEnv;
	}

	/*
	 * Returns a map of Strings parsed from a given array of attributes in a
	 * form of 'key=value'.
	 */
	public static Map<String, String> asAttributesMap(String[] attributesArray) {
		Map<String, String> map = new HashMap<String, String>();
		if (attributesArray == null) {
			return map;
		}
		for (String attribute : attributesArray) {
			try {
				int index = attribute.indexOf('=');
				map.put(attribute.substring(0, index), attribute
						.substring(index + 1));
			} catch (Exception e) {
				Logger.logException("Error while parsing launch attribute '"
						+ attribute + '\'', e);
			}
		}
		return map;
	}

	/**
	 * Creates and returns a command line invocation string for the execution of
	 * the PHP script.
	 * 
	 * @param configuration
	 *            Launch configuration
	 * @param phpExe
	 *            PHP Executable path
	 * @param phpConfigDir
	 *            PHP configuration file location (directory where php.ini
	 *            resides)
	 * @param scriptPath
	 *            Script path
	 * @param phpIniLocation
	 *            PHP configuration file path
	 * @param args
	 *            Command line arguments, if using PHP CLI, otherwise -
	 *            <code>null</code>
	 * @return commands array
	 * @throws CoreException
	 */
	public static String[] getCommandLine(ILaunchConfiguration configuration,
			String phpExe, String phpConfigDir, String scriptPath, String[] args)
			throws CoreException {
		// Check if we should treat ASP tags as PHP tags
		String aspTags = "off";

		List<String> cmdLineList = new LinkedList<String>();
		cmdLineList.addAll(Arrays.asList(new String[] { phpExe, "-c",
				phpConfigDir, "-d", "asp_tags=" + aspTags, scriptPath }));
		if (args != null) {
			cmdLineList.addAll(Arrays.asList(args));
		}
		return cmdLineList.toArray(new String[cmdLineList.size()]);
	}

	@SuppressWarnings("restriction")
	class ProcessCrashDetector2 extends ProcessCrashDetector {

		private ILaunch launch;

		public ProcessCrashDetector2(ILaunch launch, Process p, PHPExecutableDebuggerInitializer phpExecutableDebuggerInitializer) {
			super(launch, p,phpExecutableDebuggerInitializer);
			this.launch = launch;
		}

		public void run() {
			super.run();

			// In case this thread ended and we do not have any IDebugTarget
			// (PHPDebugTarget) hooked in the
			// launch that was created, we can tell that there is something
			// wrong, and probably there is no debugger
			// installed (e.g. the debugger dll/so is not properly configured in
			// the php.ini).
			if (launch != null && launch.getDebugTarget() == null) {
				String launchName = "php console";
				boolean isRunMode = ILaunchManager.RUN_MODE.equals(launch
						.getLaunchMode());
				String msg = null;
				if (isRunMode) {
					msg = MessageFormat.format(
							PHPDebugCoreMessages.Debugger_Error_Message_3,
							new Object[] { launchName });
				} else {
					msg = MessageFormat.format(
							PHPDebugCoreMessages.Debugger_Error_Message_2,
							new Object[] { launchName });
				}
				final String message = msg;
				Display.getDefault().asyncExec(new Runnable() {
					public void run() {
						MessageDialog.openWarning(Display.getDefault()
								.getActiveShell(),
								PHPDebugCoreMessages.Debugger_Launch_Error,
								message);
						DebugPlugin.getDefault().getLaunchManager()
								.removeLaunch(launch);
					}
				});
			}
		}
	}

	private static String[] asAttributesArray(Map<String, String> attributesMap) {
		String[] attributes = new String[attributesMap.size()];
		int index = 0;
		for (Map.Entry<String, String> entry : attributesMap.entrySet()) {
			attributes[index++] = entry.getKey() + '=' + entry.getValue();
		}
		return attributes;
	}
}